"""
Sing-box 配置文件管理器
通过修改配置文件并重载的方式管理代理
"""
import json
import os
import signal
import hashlib
from typing import Dict, Tuple, Optional
from pathlib import Path
from api.utils.logger import get_logger

logger = get_logger("singbox_config_manager")

CONFIG_FILE = "runtime/singbox/service_config.json"
PID_FILE = "runtime/singbox/singbox.pid"


class SingBoxConfigFileManager:
    """通过配置文件管理 Sing-box 代理"""
    
    @staticmethod
    def _ensure_config_exists():
        """确保配置文件和目录存在"""
        config_dir = os.path.dirname(CONFIG_FILE)
        if not os.path.exists(config_dir):
            os.makedirs(config_dir, exist_ok=True)
            logger.info(f"创建配置目录: {config_dir}")
        
        if not os.path.exists(CONFIG_FILE):
            # 创建初始配置
            initial_config = {
                "log": {"level": "info", "timestamp": True},
                "dns": {
                    "servers": [
                        {
                            "tag": "cloudflare-doh",
                            "address": "https://177cowl5hr.cloudflare-gateway.com/dns-query",
                            "detour": "proxy-dns"
                        },
                        {
                            "tag": "google-doh",
                            "address": "https://dns.google/dns-query",
                            "detour": "proxy-dns"
                        },
                        {
                            "tag": "local",
                            "address": "223.5.5.5",
                            "detour": "direct"
                        }
                    ],
                    "strategy": "prefer_ipv4",
                    "final": "cloudflare-doh"
                },
                "inbounds": [],
                "outbounds": [
                    {"type": "direct", "tag": "direct"},
                    {"type": "direct", "tag": "proxy-dns"}
                ],
                "route": {
                    "rules": [],
                    "final": "direct"
                }
            }
            with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
                json.dump(initial_config, f, indent=2, ensure_ascii=False)
            logger.info(f"创建初始配置文件: {CONFIG_FILE}")
    
    @staticmethod
    def _read_config() -> Dict:
        """读取 Sing-box 配置文件"""
        try:
            # 确保配置文件存在
            SingBoxConfigFileManager._ensure_config_exists()
            
            with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            logger.error(f"读取配置文件失败: {e}")
            raise
    
    @staticmethod
    def _write_config(config: Dict) -> bool:
        """写入 Sing-box 配置文件"""
        try:
            # 备份原配置
            backup_file = f"{CONFIG_FILE}.backup"
            if os.path.exists(CONFIG_FILE):
                with open(CONFIG_FILE, 'r') as f:
                    with open(backup_file, 'w') as bf:
                        bf.write(f.read())
            
            # 写入新配置
            with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            
            logger.info("配置文件已更新")
            return True
        except Exception as e:
            logger.error(f"写入配置文件失败: {e}")
            return False
    
    @staticmethod
    def _reload_singbox() -> bool:
        """发送信号让 Sing-box 重新加载配置"""
        try:
            # 延迟导入避免循环依赖
            from api.service.singbox_process_manager import SingBoxProcessManager
            
            # 检查进程是否在运行
            if not SingBoxProcessManager._is_process_running():
                logger.warning("Sing-box进程未运行，尝试启动...")
                success, message = SingBoxProcessManager._start_process()
                return success
            
            # 使用ProcessManager的重载方法
            success, message = SingBoxProcessManager._reload_process()
            if not success:
                logger.error(f"重载失败: {message}")
            return success
        except Exception as e:
            logger.error(f"重载 Sing-box 失败: {e}", exc_info=True)
            return False
    
    @staticmethod
    def _update_dns_selector(config: Dict):
        """扫描所有代理出口并更新DNS选择器"""
        proxy_tags = [
            ob.get('tag') for ob in config.get('outbounds', [])
            if ob.get('tag', '').startswith('proxy_')
        ]
        
        # 确保 'direct' 始终是备用选项
        dns_outbounds = sorted(list(set(proxy_tags + ["direct"])))
        
        selector_updated = False
        # 查找并更新现有的 'proxy-dns' 选择器
        for i, ob in enumerate(config.get('outbounds', [])):
            if ob.get('tag') == 'proxy-dns':
                # 如果没有代理节点，默认全部直连
                if not proxy_tags:
                    config['outbounds'][i]['outbounds'] = ["direct"]
                    logger.info("没有代理节点，'proxy-dns' 选择器已更新为 [\"direct\"]")
                else:
                    config['outbounds'][i]['outbounds'] = dns_outbounds
                    logger.info(f"已更新 'proxy-dns' 选择器，出口: {dns_outbounds}")
                selector_updated = True
                break
        
        # 如果 'proxy-dns' 选择器不存在，则创建一个
        if not selector_updated:
            config['outbounds'].append({
                "type": "selector",
                "tag": "proxy-dns",
                "outbounds": dns_outbounds
            })
            logger.info(f"已创建 'proxy-dns' 选择器，出口: {dns_outbounds}")


    @staticmethod
    def add_proxy(tag: str, port: int, outbound_config: Dict) -> Tuple[bool, str]:
        """
        添加代理配置
        
        Args:
            tag: 代理标签（如 proxy_xxxxx）
            port: SOCKS5 监听端口
            outbound_config: outbound 配置字典
            
        Returns:
            (是否成功, 消息)
        """
        try:
            config = SingBoxConfigFileManager._read_config()
            
            # 检查是否已存在
            inbound_tag = f"in_{tag}"
            for inbound in config.get('inbounds', []):
                if inbound.get('tag') == inbound_tag:
                    logger.info(f"Inbound {inbound_tag} 已存在")
                    return True, f"代理已存在（端口 {port}）"
            
            for outbound in config.get('outbounds', []):
                if outbound.get('tag') == tag:
                    logger.info(f"Outbound {tag} 已存在")
                    return True, f"代理已存在（端口 {port}）"
            
            # 添加 inbound（启用流量嗅探）
            inbound = {
                "type": "socks",
                "tag": inbound_tag,
                "listen": "127.0.0.1",
                "listen_port": port,
                "sniff": True,
                "sniff_override_destination": True,
                "users": []
            }
            config['inbounds'].append(inbound)
            logger.info(f"添加 inbound: {inbound_tag} (端口 {port})")
            
            # 添加 outbound
            outbound_config['tag'] = tag
            config['outbounds'].append(outbound_config)
            logger.info(f"添加 outbound: {tag}")
            
            # 更新DNS选择器
            SingBoxConfigFileManager._update_dns_selector(config)
            
            # 添加路由规则
            routing_rule = {
                "inbound": [inbound_tag],
                "outbound": tag
            }
            if 'route' not in config:
                config['route'] = {'rules': []}
            config['route']['rules'].append(routing_rule)
            logger.info(f"添加路由规则: {inbound_tag} -> {tag}")
            
            # 写入配置
            if not SingBoxConfigFileManager._write_config(config):
                return False, "写入配置文件失败"
            
            # 重载 Sing-box
            if not SingBoxConfigFileManager._reload_singbox():
                return False, "重载 Sing-box 失败"
            
            logger.info(f"代理添加成功: {tag} (端口 {port})")
            return True, f"代理添加成功（端口 {port}）"
            
        except Exception as e:
            logger.error(f"添加代理失败: {e}", exc_info=True)
            return False, f"添加代理失败: {str(e)}"
    
    @staticmethod
    def remove_proxy(tag: str) -> Tuple[bool, str]:
        """
        移除代理配置
        
        Args:
            tag: 代理标签
            
        Returns:
            (是否成功, 消息)
        """
        try:
            config = SingBoxConfigFileManager._read_config()
            
            inbound_tag = f"in_{tag}"
            
            # 移除 inbound
            config['inbounds'] = [
                ib for ib in config.get('inbounds', [])
                if ib.get('tag') != inbound_tag
            ]
            
            # 移除 outbound
            config['outbounds'] = [
                ob for ob in config.get('outbounds', [])
                if ob.get('tag') != tag
            ]
            
            # 移除路由规则
            if 'route' in config and 'rules' in config['route']:
                config['route']['rules'] = [
                    rule for rule in config['route']['rules']
                    if tag not in rule.get('outbound', '') and inbound_tag not in rule.get('inbound', [])
                ]
           
            # 更新DNS选择器以反映移除的代理
            SingBoxConfigFileManager._update_dns_selector(config)

            # 写入配置
            if not SingBoxConfigFileManager._write_config(config):
                return False, "写入配置文件失败"
            
            # 重载 Sing-box
            if not SingBoxConfigFileManager._reload_singbox():
                return False, "重载 Sing-box 失败"
            
            logger.info(f"代理移除成功: {tag}")
            return True, "代理移除成功"
            
        except Exception as e:
            logger.error(f"移除代理失败: {e}", exc_info=True)
            return False, f"移除代理失败: {str(e)}"
    
    @staticmethod
    def list_proxies() -> list:
        """列出所有代理配置"""
        try:
            config = SingBoxConfigFileManager._read_config()
            proxies = []
            
            for outbound in config.get('outbounds', []):
                tag = outbound.get('tag', '')
                if tag.startswith('proxy_'):
                    # 查找对应的 inbound
                    inbound_tag = f"in_{tag}"
                    port = None
                    for inbound in config.get('inbounds', []):
                        if inbound.get('tag') == inbound_tag:
                            port = inbound.get('listen_port')
                            break
                    
                    proxies.append({
                        'tag': tag,
                        'port': port,
                        'protocol': outbound.get('type')
                    })
            
            return proxies
        except Exception as e:
            logger.error(f"列出代理失败: {e}")
            return []


class SingBoxConfigManager:
    """Sing-box 配置管理器 - 管理代理配置的持久化和恢复"""
    
    @staticmethod
    def restore_proxy_configs(db) -> tuple[int, int]:
        """从数据库恢复所有代理配置到内存，并生成最终配置文件"""
        try:
            from api.model.proxy import Proxy
            from api.service.singbox_process_manager import SingBoxProcessManager
            from api.utils.singbox_proxy_helper import SingBoxProxyManager as Helper
            import json

            proxies = db.query(Proxy).filter(Proxy.type.in_(['vmess', 'vless', 'trojan'])).all()
            
            if not proxies:
                logger.info("没有需要恢复的代理配置")
                SingBoxProcessManager._regenerate_config_file_only()
                return 0, 0

            logger.info(f"开始恢复 {len(proxies)} 个代理配置到内存...")
            
            success_count = 0
            fail_count = 0
            used_ports = set()

            for proxy in proxies:
                try:
                    if not proxy.v2ray_config:
                        fail_count += 1
                        continue
                    
                    v2ray_config = json.loads(proxy.v2ray_config)
                    config_hash = Helper._generate_config_hash(v2ray_config)
                    tag = f"proxy_{config_hash[:8]}"
                    
                    # 分配端口
                    port = Helper.find_free_port(exclude_ports=used_ports)
                    used_ports.add(port)
                    
                    # 直接填充到ProcessManager的内存中
                    SingBoxProcessManager._proxy_configs[tag] = {
                        'port': port,
                        'v2ray_config': v2ray_config,
                        'ref_count': 1
                    }
                    logger.info(f"✓ 代理 {proxy.id} ({proxy.name}) 已加载到内存: tag={tag}, port={port}")
                    success_count += 1
                except Exception as e:
                    logger.error(f"✗ 代理 {proxy.id} ({proxy.name}) 加载失败: {e}")
                    fail_count += 1
            
            logger.info(f"内存加载完成: 成功 {success_count} 个, 失败 {fail_count} 个")
            
            # 一次性生成最终配置文件
            logger.info("正在生成最终的Sing-box配置文件...")
            gen_success, gen_message = SingBoxProcessManager._regenerate_config_file_only()
            if gen_success:
                logger.info("✓ 配置文件生成成功")
            else:
                logger.error(f"✗ 配置文件生成失败: {gen_message}")

            return success_count, fail_count

        except Exception as e:
            logger.error(f"恢复代理配置时发生严重错误: {e}", exc_info=True)
            return 0, len(proxies) if 'proxies' in locals() else 0
    
    @staticmethod
    def clear_all_proxy_configs():
        """清除所有动态添加的代理配置"""
        try:
            from api.utils.singbox_proxy_helper import _running_singbox_processes, SingBoxProxyManager
            
            if not _running_singbox_processes:
                logger.info("没有需要清除的代理配置")
                return
            
            config_hashes = list(_running_singbox_processes.keys())
            for config_hash in config_hashes:
                try:
                    success, message = SingBoxProxyManager.remove_proxy_config(config_hash)
                    if success:
                        logger.info(f"已移除代理配置: {config_hash[:8]}")
                    else:
                        logger.error(f"移除代理配置失败 {config_hash[:8]}: {message}")
                        
                except Exception as e:
                    logger.error(f"移除代理配置时出错: {e}")
            
            logger.info("所有代理配置已清除")
            
        except Exception as e:
            logger.error(f"清除代理配置失败: {e}")


def init_singbox_configs(db):
    """初始化Sing-box配置（在应用启动时调用）"""
    logger.info("=" * 50)
    logger.info("初始化Sing-box代理配置...")
    
    success, fail = SingBoxConfigManager.restore_proxy_configs(db)
    
    if success > 0:
        logger.info(f"✓ 成功加载 {success} 个代理配置到内存")
    if fail > 0:
        logger.warning(f"✗ {fail} 个代理配置加载失败")
    
    logger.info("=" * 50)